#include "HeadCounter.h"
#include "TStringCast.h"
#include "TStringUtil.h"
#include "IniFile.h"
#include "CvUtil.h"


HeadCounter::HeadCounter()
{

}

HeadCounter::~HeadCounter()
{

}

/**
 * 统计人头
 * @param src	输入图像
 * @param count	人头个数
 * @return
 */
cv::Mat HeadCounter::countHead(cv::Mat& src, int& count)
{
	std::vector<std::vector<cv::Point> > contours;
	return countHead(src, count, contours);
}

/**
 * 统计人头, 获取人头的轮廓
 * @param src
 * @param count
 * @param contours
 * @return
 */
cv::Mat HeadCounter::countHead(cv::Mat& src, int& count, std::vector<std::vector<cv::Point> >& contours)
{
	/// 将颜色转换为HSV格式
	cv::Mat hsvMat;
	cvtColor(src, hsvMat, CV_BGR2HSV);

	/// 生成掩码图像(人头所在区域)
	cv::Mat maskMat = buildMask(hsvMat);

	cv::Mat cutMat;
	hsvMat.copyTo(cutMat, maskMat);

	/// 平滑
	cv::Mat blurMat;
	cv::blur(cutMat, blurMat, cv::Size(3, 3));

	/// 创建空的轮廓
	cv::Mat outlineMat = cv::Mat::zeros(src.size(), CV_8UC1);

	count = 0;

	for (size_t i = 0; i < m_params.colorRanges.size(); i++)
	{
		cv::Scalar& lowColor = m_params.colorRanges[i].low;
		cv::Scalar& delta = m_params.colorRanges[i].delta;

		/// 根据颜色查找轮廓
		count += findColorContours(blurMat, lowColor, delta, m_params.arc, m_params.area, contours);
	}

	cv::CvUtil::distinctContours(src.size(), contours);
	count = contours.size();

	cv::Scalar color(0, 255, 0);
	for (size_t i = 0; i < contours.size(); i++)
	{
		//cv::approxPolyDP(contours[i], contours[i], 7, false);
		//cv::drawContours(src, contours, i, color, 1);
	}

	//drawNum(src, count, cv::Scalar(0,255,0));

	return src;
}

/**
 * 在图像上绘制数字文本
 * @param mat
 * @param count
 * @param color
 */
void HeadCounter::drawNum(cv::Mat& mat, int count, cv::Scalar color)
{
	std::string text = comn::StringUtil::format("%d", count); // comn::StringCast::toString(count);
	int fontFace = cv::FONT_HERSHEY_SIMPLEX;
	double fontScale = 2;
	cv::Point point(mat.size().width / 2, mat.size().height / 2);
	cv::putText(mat, text, point, fontFace, fontScale, color, 2);
}

/**
 * 从配置文件加载颜色
 * @param iniFile
 * @param section
 * @param name
 * @param scalar
 * @return
 */
static bool loadColorRange(comn::IniFile& iniFile, const char* section, const char* name, cv::Scalar& scalar)
{
	std::string value;
	if (!iniFile.queryValue(section, name, value))
	{
		return false;
	}

	std::vector< std::string > strings;
	comn::StringUtil::split(value, ',', strings);

	for (size_t i = 0; i < strings.size(); i++)
	{
		comn::StringCast::toValue(strings[i], scalar[i]);
	}

	return true;
}

/**
 * 加载颜色
 * @param iniFile
 * @param section
 * @param colorRange
 * @return
 */
static bool loadColorRange(comn::IniFile& iniFile, const char* section, HeadCounter::ColorRange& colorRange)
{
	colorRange.name = section;
	iniFile.queryValue(section, "Name", colorRange.name);
	bool found = loadColorRange(iniFile, section, "Low", colorRange.low);
	loadColorRange(iniFile, section, "Delta", colorRange.delta);
	return found;
}

bool HeadCounter::load(const char* filename, Params& params)
{
	comn::IniFile iniFile;
	if (!iniFile.load(filename))
	{
		return false;
	}

	params.colorRanges.clear();

	iniFile.queryValue("General", "MinArc", params.arc);
	iniFile.queryValue("General", "MinArea", params.area);
	
	int count = 0;
	iniFile.queryValue("General", "Color", count);

	//loadColorRange(iniFile, "Background", params.background);

	for (int i = 0; i < count; i++)
	{
		std::string section = comn::StringUtil::format("Color_%d", i + 1);
		
		ColorRange colorRange;
		if (loadColorRange(iniFile, section.c_str(), colorRange))
		{
			params.colorRanges.push_back(colorRange);
		}
	}

	return true;
}

bool HeadCounter::loadParams(const char* filename)
{
	return load(filename, m_params);
}

cv::Mat HeadCounter::buildMask(cv::Mat& src)
{
	return makeForeground(src);
}

/**
 * 根据颜色偏差范围查找轮廓
 * @param srcMat
 * @param lowColor
 * @param delta
 * @param outContours
 * @return
 */
int HeadCounter::findColorContours(cv::Mat& srcMat, cv::Scalar lowColor, cv::Scalar delta,
	std::vector<std::vector<cv::Point> >& outContours)
{
	cv::Scalar lowbound = lowColor;
	cv::Scalar upbound;
	cv::add(lowColor, delta, upbound);

	cv::Mat binMat = cv::Mat::zeros(srcMat.size(), CV_8UC1);
	cv::inRange(srcMat, lowbound, upbound, binMat);

	std::vector<std::vector<cv::Point> > contours;
	std::vector<cv::Vec4i> hierarchy;

	cv::findContours(binMat, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

	for (size_t i = 0; i < contours.size(); ++i)
	{
		outContours.push_back(contours[i]);
	}

	return contours.size();
}

cv::Mat HeadCounter::makeForeground(cv::Mat& src)
{
	return makeForeground(src, 70, 70 * 3, 20);
}

/**
 * 生成前景区域
 * @param src
 * @param lowThresh
 * @param highThresh
 * @param minRadius
 * @return
 */
cv::Mat HeadCounter::makeForeground(cv::Mat& src, double lowThresh, double highThresh, int minRadius)
{
	/// 平滑
	cv::Mat blurMat;
	cv::blur(src, blurMat, cv::Size(3, 3));

	/// 转换为灰度图
	cv::Mat binMat;
	cv::cvtColor(blurMat, binMat, CV_BGR2GRAY);

	/// canny算法生成边缘
	cv::Mat edgeMat;
	cv::Canny(binMat, edgeMat, lowThresh, highThresh);

	cv::Mat rectMat = makeContourRectMat(edgeMat, 0);
	return makeContourRectMat(rectMat, minRadius);
}

/**
 * 从轮廓获取外围矩形
 * @param binMat
 * @param minRadius
 * @return
 */
cv::Mat HeadCounter::makeContourRectMat(cv::Mat& binMat, int minRadius)
{
	std::vector<std::vector<cv::Point> > contours;
	std::vector<cv::Vec4i> hierarchy;

	cv::findContours(binMat, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

	cv::Mat rectMat = cv::Mat::zeros(binMat.size(), CV_8UC1);
	cv::Scalar color(255, 0, 0);
	for (auto contour : contours)
	{
		cv::Rect rect = cv::boundingRect(contour);

		if (rect.width < minRadius || rect.height < minRadius)
		{
			continue;
		}

		cv::Point points[4] = { rect.tl() , rect.tl() , rect.br(), rect.br() };
		cv::CvUtil::getPoints(rect, points);

		cv::fillConvexPoly(rectMat, points, 4, color);
	}
	return rectMat;
}

/**
 * 根据颜色偏差和弧长,面积获取轮廓
 * @param srcMat
 * @param lowColor
 * @param delta
 * @param minArc
 * @param minArea
 * @param outContours
 * @return
 */
int HeadCounter::findColorContours(cv::Mat srcMat, cv::Scalar lowColor, cv::Scalar delta, int minArc, double minArea,
	std::vector<std::vector<cv::Point> >& outContours)
{
	std::vector<std::vector<cv::Point> > contours;
	findColorContours(srcMat, lowColor, delta, contours);

	auto contourFilter = [=](std::vector<cv::Point>& contour) -> bool
	{
		int arc = contour.size();
		double area = cv::contourArea(contour);

		if (arc < minArc || area < minArea)
		{
			return true;
		}

		return false;
	};

	auto newEnd = std::remove_if(contours.begin(), contours.end(), contourFilter);
	contours.erase(newEnd, contours.end());

	for (auto contour : contours)
	{
		outContours.push_back(contour);
	}

	int count = contours.size();
	return count;
}
